package com.wn.system.service.impl;

import java.math.BigDecimal;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.wn.access.api.BankAccessApi;
import com.wn.common.constant.redis.RedisConstant;
import com.wn.common.constant.user.UserConstant;
import com.wn.common.exception.Assert;
import com.wn.common.model.dto.CardAddDto;
import com.wn.common.model.dto.LoginUserDto;
import com.wn.common.model.dto.UserRegisterDto;
import com.wn.common.model.dto.bank.BankCardAddDto;
import com.wn.common.model.vo.LoginVo;
import com.wn.common.model.vo.TokenVo;
import com.wn.common.result.Result;
import com.wn.common.result.enums.impl.BusinessCode;
import com.wn.common.utils.JwtTemplate;
import com.wn.common.utils.TokenUtil;
import com.wn.system.controller.UserController;
import com.wn.system.entity.User;
import com.wn.system.entity.UserCard;
import com.wn.system.entity.UserDetail;
import com.wn.system.mapper.UserDetailMapper;
import com.wn.system.mapper.UserMapper;
import com.wn.system.service.IUserCardService;
import com.wn.system.service.IUserDetailService;
import com.wn.system.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author yellow docker
 * @since 2023-07-20
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserConstant, RedisConstant {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private IUserDetailService userDetailService;

    @Autowired
    private IUserCardService userCardService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private JwtTemplate jwtTemplate;

    @Autowired
    private BankAccessApi bankAccessApi;

    /**
     * 注册功能
     * @param userRegisterDto
     */
    @Transactional
    @Override
    public void register(UserRegisterDto userRegisterDto) {

        //01  通过手机号判断  这个手机号码是否存在用户 、存在 就是已注册
        boolean exist = checkUserExist(userRegisterDto.getPhone());

        //用户已存在
        Assert.error(exist, BusinessCode.USER_EXISTED);

        saveUserInfo(userRegisterDto);
    }

    /**
     * 用户的注册操作
     * @param userRegisterDto
     */
    private void saveUserInfo(UserRegisterDto userRegisterDto) {
        //02  往 user表中添加 数据
        User user = BeanUtil.copyProperties(userRegisterDto, User.class);

        //把密码进行加密
        user.setPwd(passwordEncoder.encode(userRegisterDto.getPwd()));

        //设置类型   个人 (通过实现接口来使用常量)
        user.setUserType(USER_TYPE_PERSONAL);
        user.setUserStatus(true);

        save(user);

        //03  往 user_detail 表中添加数据
        UserDetail userDetail = BeanUtil.copyProperties(userRegisterDto, UserDetail.class);

        //MP 添加数据后 会自动把id 返回给当前的对象

        userDetail.setId(user.getId());
        //user-code 码 随机
        userDetail.setUserCode(UUID.randomUUID().toString());
        //借款  默认 0
        userDetail.setBorrowMoney(new BigDecimal("0"));
        //是否绑卡 未绑卡
        userDetail.setCardBindStatus(false);

        userDetailService.save(userDetail);
    }

    /**
     * 登录的业务逻辑实现
     *
     * @param loginUserDto
     * @return 具体的业务实现:
     * 手机登录 或者用户名登录
     * 1、手机登录
     *      1、判断 验证码是否 已过期 （redis中是否存在验证码）
     *      2、判断验证码   ok  返回登录的用户
     *          第一次都是可以实现 验证码登录 (判断是否是第一次登录)
     *          如果是第一次登录 自动 给你进行注册
     *          注册完毕后 返回 注册用户 给你
     * <p>
     * 2、用户名登录
     * 1、通过用户名查询 用户信息对象
     * 2、通过用户信息对象 和 输入的密码进行校验
     *
     */
    @Override
    public LoginVo login(LoginUserDto loginUserDto) {

        boolean phone = loginUserDto.isPhone();

        User user = null;
        // 手机登录
        if (phone) {

            //1、手机登录
            user = smsLoginPhone(loginUserDto);

        } else {
            //2、用户名登录
            user = usernameLogin(loginUserDto);
        }

        //01 生成token
        Map<String,Object> payload = new HashMap<>();
        payload.put("USER_ID",user.getId());
        payload.put("PHONE",user.getPhone());

        String token = jwtTemplate.createToken(payload);

        //02 存储token到redis
        stringRedisTemplate.opsForValue().set(
                "USER_ID:TOKEN:" + user.getId(),
                token
//                Duration.ofMinutes(300L)
        );
        //03 返回token 给前端
        LoginVo loginVo = BeanUtil.copyProperties(user, LoginVo.class);
        loginVo.setToken(token);

        return loginVo;
    }

    /**
     *  银行绑卡
     *      *  2、绑卡的业务
     *      *   1、获取token中登录用户
     *          2、判断当前用户是否已经绑定银行卡
     *          *        Assert.error(userDetail.getCardBindStatus(), BusinessCode.USER_ALREADY_BINDED);
     *          *        查询userDetail 表中的  CARD_BIND_STATUS 字段状态  (未绑卡是0)
     *          3、判断提交过来的银行卡是否已被绑定
     *          *        （绑卡业务请求的时候、会把银行的id传递过来）
     *          *          通过银行卡id 查询 UserCard (是否有存在的数据)  存在、已绑卡
     *          4、更新用户的信息、更新用户明细、保存银行卡信息（user_card表）、
     *                   绑卡的 在  UserCard  表中(添加 UserCard 的数据)
     *
     *      分析:
     *       1、表结构
     *              user 表  基本的用户信息
     *              user_detail 表  用户详情表
     *              user_card   表  用户绑卡的表
     *
     *       2、业务和表的字段进行对应
     *         1、 判断当前用户是否已经绑定银行卡
     *            查询判断  user_detail下面的   CARD_BIND_STATUS 字段  (0 未绑卡 1、绑卡)
     *
     *         2、判断提交过来的银行卡是否已被绑定
     *            查询  user_card 表中的数据 是否存在、存在就是以绑卡
     *
     *         3、 更新用户的信息
     *                 注册的时候、没有真实姓名和 id (身份证)
     *                 通过  CardAddDto 提供的  姓名 和 id 进行更新
     *
     *         4、往 UserCard 表中添加数据
     */

    /**
     *  测试:
     *    查看数据库的表的结构变化:
     *    1、   User         更新:   realName、id             用户表
     *    2、   UserCard     添加新的数据                     用户绑卡的表
     *
     *    2、托管系统
     *         tradeLog 表   添加了日志
     */
    @Override
    public void addBankAccount(CardAddDto cardAddDto) {

        //01 获取token 信息
        TokenVo tokenVo = TokenUtil.getTokenVo();
        System.out.println("tokenVo = " + tokenVo);

        //02 绑卡业务
        //1、判断当前用户是否已经绑定银行卡

        //通过  userId 查询 userDetail  判断 是否绑卡没有
        UserDetail userDetail = userDetailService.getOne(Wrappers.lambdaQuery(UserDetail.class)
                .eq(UserDetail::getId, tokenVo.getUserId()));

        Assert.error(userDetail.getCardBindStatus(),BusinessCode.USER_CARD_ALREADY_BINDED);

        //2、判断提交过来的银行卡是否已被绑定

        //通过 cardNo 查询 userCard  判断 是否绑卡没有
        UserCard userCard = userCardService.getOne(Wrappers.lambdaQuery(UserCard.class)
                .eq(UserCard::getCardNo, cardAddDto.getCardNo()));
        Assert.error(Objects.nonNull(userCard),BusinessCode.USER_CARD_ALREADY_BINDED);

        //3、更新用户的信息
        // 通过  用户id 更新   真实姓名 和 身份证id
        update(Wrappers.lambdaUpdate(User.class)
                .set(User::getRealName,cardAddDto.getRealName())
                .set(User::getIdentityCode,cardAddDto.getIdentityCode())
                .eq(User::getId,tokenVo.getUserId())
        );

        //04、提交 userCard 信息
        UserCard uCard = new UserCard();
        //用户id
        uCard.setUserId(tokenVo.getUserId());
//        uCard.setBankNo("");      银行办卡同意后 来更新这数据
//        uCard.setBankTitle("");
        //添加银行卡号
        uCard.setCardNo(cardAddDto.getCardNo());
        uCard.setPhone(tokenVo.getPhone());
        uCard.setStatus(false);
        userCardService.save(uCard);

        //05  远程调用托管服务 (所有的操作 都需 给托管服务处理 (绑卡、借款) )
        //    1、日志记录
        //    2、对接银行

        // 银行卡 绑定 dto
        BankCardAddDto bankCardAddDto = new BankCardAddDto();

        //请求 流水号
        bankCardAddDto.setReqCode("REQ_" + UUID.randomUUID().toString().substring(0,8));
        bankCardAddDto.setUserId(tokenVo.getUserId());
        Result result = bankAccessApi.bankCardAddDto(bankCardAddDto);

        Object data = result.getData();
        System.out.println("data = " + data);
    }

    private User usernameLogin(LoginUserDto loginUserDto) {

        //01 查询用户
        String username = loginUserDto.getUsername();
        User user = checkUser(username);

        //02 密码校验

        return user;
    }

    /**
     * 手机验证码登录
     * @param loginUserDto
     * @return
     *
     *      *      1、判断 验证码是否 已过期 （redis中是否存在验证码）
     *      *      2、判断验证码   ok  返回登录的用户
     *      *          第一次都是可以实现 验证码登录 (判断是否是第一次登录)
     *      *          如果是第一次登录 自动 给你进行注册
     *      *          注册完毕后 返回 注册用户 给你
     *
     */
    private User smsLoginPhone(LoginUserDto loginUserDto) {

        //01 获取验证码
        String phone = loginUserDto.getUsername();

        String key = SMS_PREFIX + phone;
        String redisCode = stringRedisTemplate.opsForValue().get(key);
        Assert.error(redisCode,"验证码以过期...");
        //02 校验验证码
        String code = loginUserDto.getPassword();
        //抛出验证码错误
        Assert.error(!redisCode.equals(code),BusinessCode.USER_VERIFY_CODE_ERROR);

        //03  是否 是第一次 登录 （自动注册用户）
        User user= checkUser(phone);

        if (Objects.isNull(user)){

            //第一次登录 (自动注册用户)
            UserRegisterDto userRegisterDto = new UserRegisterDto();
            //用户名 随机一个字符串
            userRegisterDto.setUserName("admin" + UUID.randomUUID().toString().substring(0,4));

            userRegisterDto.setPhone(phone);
            userRegisterDto.setPwd("123");

            //自动注册
            saveUserInfo(userRegisterDto);

            //如果是第一次需要 再次查询一次
            return  checkUser(phone);
        }

        return user;
    }

    /**
     * 判断用户是否存在
     * @param phone
     * @return
     */
    private User checkUser(String phone) {

        //查询条件构造器
        LambdaQueryWrapper<User> userLambdaQueryWrapper = Wrappers.lambdaQuery(User.class);
        userLambdaQueryWrapper.eq(User::getPhone,phone);

        //条件查询  返回user
        return this.getOne(userLambdaQueryWrapper);
    }

    private boolean checkUserExist(String phone) {

        //创建 lambda 条件对象 (通过手机构建条件查询对象)
        LambdaQueryWrapper<User> userLambdaQueryWrapper = Wrappers.lambdaQuery(User.class)
                .eq(User::getPhone, phone);

        // 通过手机条件进行查询操作
        User user = getOne(userLambdaQueryWrapper);
        return Objects.nonNull(user);

    }
}
